<?php


namespace App\Services;


use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;

class WechatEnterprisePay
{
    public $app_id;

    public $mch_id;

    public $key;

    public $cert;

    public $secret;

    private $public_key = 'cert/wechat/public_key.pem';

    private $public_rsa = '-----BEGIN PUBLIC KEY-----

-----END PUBLIC KEY-----
';

    public function __construct($app_id,$mch_id,$key,$cert,$secret)
    {
        $this->app_id = $app_id;
        $this->mch_id = $mch_id;
        $this->key = $key;
        $this->cert = $cert;
        $this->secret = $secret;
    }

    public function payChange(string $out_trade_no, string $openid, int $amount, string $name, string $desc)
    {
        $data = [
            'mch_appid'         => $this->app_id,
            'mchid'             => $this->mch_id,
            'partner_trade_no'  => $out_trade_no,
            'nonce_str'         => $this->random(32),
            'openid'            => $openid,
            'check_name'        => 'FORCE_CHECK',
            're_user_name'      => $name,
            'amount'            => $amount,
            'desc'              => $desc,
        ];

        $data['sign'] = $this->getSign($data);

        try {
            return $this->HttpPost(
                'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers',
                    $this->arrayToXml($data)
                );
        }catch (\Exception $exception){
            $this->errorLog('微信付款到零钱异常，mch_id：'.$this->mch_id,[$exception->getMessage()]);

            return false;
        }
    }


    /**
     * 付款到银行卡
     * @param string $out_trade_no
     * @param int $amount
     * @param string $bank_name
     * @param string $bank_card
     * @param string $name
     * @param string $desc
     * @return false|mixed
     */
    public function payBank(string $out_trade_no,int $amount, string $bank_name, string $bank_card, string $name, string $desc)
    {
        $data = [
            'mch_id'            => $this->mch_id,
            'partner_trade_no'  => $out_trade_no,
            'nonce_str'         => $this->random(32),
            'enc_bank_no'       => $this->publicEncrypt($bank_card),
            'enc_true_name'     => $this->publicEncrypt($name),
            'bank_code'         => $this->getBankCode($bank_name),
            'amount'            => $amount,
            'desc'              => $desc,
        ];
        $data['sign'] = $this->getSign($data);

        $url = 'https://api.mch.weixin.qq.com/mmpaysptrans/pay_bank';

        try {
            return $this->HttpPost($url,$this->arrayToXml($data));
        }catch (\Exception $exception){

            $this->errorLog('微信付款到银行卡异常，mch_id：'.$this->mch_id,[$exception->getMessage()]);

            return false;
        }

    }

    /**
     * 查询付款记录
     * @param $payment_no
     * @return false|mixed
     */
    public function queryOrder($payment_no)
    {
        $data = [
            'mch_id'            => $this->mch_id,
            'partner_trade_no'  => $payment_no,
            'nonce_str'         => $this->random(30),
        ];
        $data['sign'] = $this->getSign($data);

        $url = 'https://api.mch.weixin.qq.com/mmpaysptrans/query_bank';

        try {
            return $this->HttpPost($url,$this->arrayToXml($data));
        }catch (\Exception $exception){

            $this->errorLog('微信付款到银行卡查询异常，mch_id：'.$this->mch_id,[$exception->getMessage()]);

            return false;
        }
    }

    /**
     * 获取公钥,格式为PKCS#1 转PKCS#8
     * openssl rsa -RSAPublicKey_in -in  <filename> -out <out_put_filename>
     *
     */
    private function get_pub_key()
    {

        if (! Storage::exists($this->public_key)){
            $data['mch_id'] = $this->mch_id;
            $data['nonce_str'] = $this->random(12);
            $sign = $this->getSign($data);
            $dataXML='<xml>
                  <mch_id>'.$data['mch_id'].'</mch_id>
                  <nonce_str>'.$data['nonce_str'].'</nonce_str>
                  <sign>'.$sign.'</sign>
                  </xml>';

            $url = 'https://fraud.mch.weixin.qq.com/risk/getpublickey';
            $ret = $this->HttpPost($url,$dataXML,true);

            if($ret['return_code'] == 'SUCCESS' && isset($ret['pub_key'])){
                Storage::put($this->public_key,$ret['pub_key']);
                return $ret['pub_key'];
            }else{
                return null;
            }
        }else{

            return Storage::get($this->public_key);
        }


    }

    public function getBankCode(string $bank_name)
    {
        $bank_code = 0;
        switch ($bank_name){
            case '工商银行':  $bank_code = 1002; break;
            case '农业银行':  $bank_code = 1005; break;
            case '中国银行':  $bank_code = 1026; break;
            case '建设银行':  $bank_code = 1003; break;
            case '招商银行':  $bank_code = 1001; break;
            case '邮储银行':  $bank_code = 1066; break;
            case '交通银行':  $bank_code = 1020; break;
            case '浦发银行':  $bank_code = 1004; break;
            case '民生银行':  $bank_code = 1006; break;
            case '兴业银行':  $bank_code = 1009; break;
            case '平安银行':  $bank_code = 1010; break;
            case '中信银行':  $bank_code = 1021; break;
            case '华夏银行':  $bank_code = 1025; break;
            case '广发银行':  $bank_code = 1027; break;
            case '光大银行':  $bank_code = 1022; break;
            case '北京银行':  $bank_code = 1032; break;
            case '宁波银行':  $bank_code = 1056; break;
        }

        return $bank_code;
    }

    /**
     * 公钥加密，银行卡号和姓名需要RSA算法加密
     * @param string $data  需要加密的字符串，银行卡/姓名
     * @return null|string  加密后的字符串
     */
    private function publicEncrypt($data)
    {
        // 进行加密
        $pubkey = openssl_pkey_get_public($this->public_rsa);
        $encrypt_data = '';
        $encrypted = '';
        $r = openssl_public_encrypt($data,$encrypt_data,$pubkey,OPENSSL_PKCS1_OAEP_PADDING);
        if($r){//加密成功，返回base64编码的字符串
            return base64_encode($encrypted.$encrypt_data);
        }else{
            return false;
        }
    }

    //对参数排序，生成MD5加密签名
    private function getSign($paramArray, $isencode=false)
    {
        $paramStr = '';
        ksort($paramArray);
        $i = 0;
        foreach ($paramArray as $key => $value) {
            if ($key == 'Signature') {
                continue;
            }
            if ($i == 0) {
                $paramStr .= '';
            } else {
                $paramStr .= '&';
            }
            $paramStr .= $key . '=' . ($isencode ? urlencode($value) : $value);
            ++$i;
        }
        $stringSignTemp = $paramStr . "&key=" . $this->key;
        $sign = strtoupper(md5($stringSignTemp));
        return $sign;
    }

    /**
     * 将xml转换成数组
     * @params xml $xml : xml数据
     * return array $data : 返回数组
     */
    private function xmlToArray($xml)
    {
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $xmlstring = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
        $val = json_decode(json_encode($xmlstring),true);
        return $val;
    }

    /**
     * 数组转xml
     * @param $arr
     * @return string
     */
    private function arrayToXml($arr)
    {
        $xml = "<xml>";
        foreach ($arr as $key=>$val){
            if(is_array($val)){
                $xml.="<".$key.">".$this->arrayToXml($val)."</".$key.">";
            }else{
                $xml.="<".$key.">".$val."</".$key.">";
            }
        }
        $xml.="</xml>";
        return $xml;
    }

    /**
     *  随机字符串
     * @param int $length 长度
     * @param string $type 类型
     * @param int $convert 转换大小写 1大写 0小写
     * @return string
     */
    private function random($length=10,$type='letter',$convert=false)
    {
        $config = array(
            'number'=>'1234567890',
            'letter'=>'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
            'string'=>'abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ23456789',
            'all'=>'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'
        );

        if(!isset($config[$type])) $type = 'letter';
        $string = $config[$type];

        $code = '';
        $strlen = strlen($string) -1;
        for($i = 0; $i < $length; $i++){
            $code .= $string[mt_rand(0, $strlen)];
        }
        if(!empty($convert)){
            $code = ($convert > 0)? strtoupper($code) : strtolower($code);
        }
        return $code;
    }
    /**
     *
     * 日志记录
     * @params string $msg : 文字描述
     * @params array $ret : 调用接口返回的数组
     */
    private function errorLog($msg,$ret)
    {
        Log::info($msg,$ret);
    }

    /**
     * 发送数据
     * @param $url
     * @param $data
     * @param bool $ssl
     * @param bool $debug
     * @return false|mixed
     * @throws \Exception
     */
    private function HttpPost($url,$data, bool $debug = false)
    {

        $client = Http::withOptions([
            'debug'         => $debug,
        ])->send('POST',$url,[
            'cert'      => storage_path($this->cert),
            'ssl_key'   => storage_path($this->secret),
            'body'  => $data,
        ]);

        if ($client->successful()){

            return $this->xmlToArray($client->body());
        }

        return false;
    }
}
